package com.mstar.qs.common.io;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import com.mstar.qs.common.URLUtil;

//import com.morningstar.qs.api.basic.util.HttpClientSimpleImpl;

public class QsHttpClient {

	/**
	 * default connection timeout time:10 seconds.
	 */
	public static final int CONNECTION_TIMEOUT = 10000;

	/**
	 * default data transmission time: 60 seconds.
	 */
	public static final int DATA_TRANSMISSION_TIMEOUT = 60000;

	private static Logger log = Logger.getLogger(QsHttpClient.class
			.getPackage().getName());

	private URLUtil urlUtil = new URLUtil();

	/**
	 * Default constructor.
	 */
	public QsHttpClient() {
	}

	/**
	 * Constructor with host parameter.
	 * 
	 * @param host
	 */
	public QsHttpClient(String host) {
		urlUtil.setHost(host);
	}

	public QsHttpClient(String host, String path) {
		urlUtil.setHost(host);
		urlUtil.setPath(path);
	}

	public QsHttpClient(String protocol, String host, int port, String path) {
		if (protocol != null)
			urlUtil.setProtocol(protocol);
		urlUtil.setHost(host);
		urlUtil.setPort(port);
		urlUtil.setPath(path);
	}

	/**
	 * Execute http request with Get method.
	 * 
	 * it is not threadsafe method
	 * 
	 * @param params
	 * @return
	 * @throws URISyntaxException
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public QueryCombination execute(LinkedHashMap<String, String> params)
			throws URISyntaxException, ClientProtocolException, IOException {
		return execute(params, CONNECTION_TIMEOUT, DATA_TRANSMISSION_TIMEOUT);
	}

	public QueryCombination execute(String host, String path,
			LinkedHashMap<String, String> params) throws URISyntaxException,
			ClientProtocolException, IOException {
		return execute(params, CONNECTION_TIMEOUT, DATA_TRANSMISSION_TIMEOUT);
	}

	public QueryCombination execute(LinkedHashMap<String, String> params,
			int connTimeOut, int dataTranTimeOut) throws URISyntaxException,
			ClientProtocolException, IOException {
		String url=urlUtil.assembleUri(params).toString();
		return execute(url, connTimeOut,
				dataTranTimeOut);
	}

	public QueryCombination execute(String host, String path,
			LinkedHashMap<String, String> params, int connTimeOut,
			int dataTranTimeOut) throws URISyntaxException,
			ClientProtocolException, IOException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		final HttpClient httpclient = new DefaultHttpClient();
		URI u = urlUtil.assembleUri(host, path, params);
		HttpGet httpget = new HttpGet(u);
		httpclient.getParams().setParameter(
				CoreConnectionPNames.CONNECTION_TIMEOUT, connTimeOut);
		httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
				dataTranTimeOut);
		HttpResponse response = httpclient.execute(httpget);
		String responseString = EntityUtils.toString(response.getEntity());
		// LogFactory.COMMON_LOGGER.info(responseString);
		return new QueryCombination(u, responseString);
	}

	/**
	 * Executes Http Get method with the complete url.
	 * 
	 * @param url
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @throws URISyntaxException
	 */
	public QueryCombination execute(String url) throws ClientProtocolException,
			IOException, URISyntaxException {
		return execute(url, CONNECTION_TIMEOUT, DATA_TRANSMISSION_TIMEOUT);
	}

	/**
	 * 
	 * @param url
	 *            it should be like http://msdxml.morningstar.com/index.php
	 * @param params
	 *            it should be simalar with
	 *            username=name&password=pwd&instruments=126.1.IBM
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @throws URISyntaxException
	 */
	public QueryCombination execute(String url,
			LinkedHashMap<String, String> params)
			throws ClientProtocolException, IOException, URISyntaxException {
		return execute(urlUtil.assembleWithSemiFinishedGoods(url, params)
				.toString(), CONNECTION_TIMEOUT, DATA_TRANSMISSION_TIMEOUT);
	}
	
	public QueryCombination execute(LinkedHashMap<String, String> params, Set<String> extParam)
			throws ClientProtocolException, IOException, URISyntaxException {
		return execute(urlUtil.assembleUri(params, extParam)
				.toString(), CONNECTION_TIMEOUT, DATA_TRANSMISSION_TIMEOUT);
	}

	public QueryCombination execute(String url, int connTimeOut,
			int dataTranTimeOut) throws ClientProtocolException, IOException,
			URISyntaxException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		final HttpClient httpclient = new DefaultHttpClient();
		// log.info(URLEncoder.encode(url, "UTF-8"));
		HttpGet httpget = new HttpGet(url);
//		log.info(url);
		httpclient.getParams().setParameter(
				CoreConnectionPNames.CONNECTION_TIMEOUT, connTimeOut);
		httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
				dataTranTimeOut);
		HttpResponse response = httpclient.execute(httpget);
		String responseString = EntityUtils.toString(response.getEntity());
		return new QueryCombination(url, responseString);
	}

	public QueryCombination executePost(String url)
			throws ClientProtocolException, IOException, URISyntaxException {
		return executePost(url, CONNECTION_TIMEOUT, DATA_TRANSMISSION_TIMEOUT);
	}

	public QueryCombination executePost(String url, int connTimeOut,
			int dataTranTimeOut) throws ClientProtocolException, IOException,
			URISyntaxException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		final HttpClient httpclient = new DefaultHttpClient();
		HttpPost httppost = new HttpPost(url);
		return post(httpclient, httppost, url, connTimeOut, dataTranTimeOut);

	}

	public QueryCombination executePost(String url, String stringBody)
			throws ClientProtocolException, IOException, URISyntaxException {
		return executePost(url, stringBody, CONNECTION_TIMEOUT,
				DATA_TRANSMISSION_TIMEOUT);

	}

	public QueryCombination executePost(String url, String stringBody,
			int connTimeOut, int dataTranTimeOut)
			throws ClientProtocolException, IOException, URISyntaxException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		return executePost(null, url, stringBody, connTimeOut, dataTranTimeOut);

	}

	public static QueryCombination executePost(Map<String, String> headers,
			String url, String stringBody) throws ClientProtocolException,
			IOException, URISyntaxException {
		return executePost(headers, url, stringBody, CONNECTION_TIMEOUT,
				DATA_TRANSMISSION_TIMEOUT);
	}

	public static QueryCombination executePost(Map<String, String> headers,
			String url, String stringBody, int connTimeOut, int dataTranTimeOut)
			throws ClientProtocolException, IOException, URISyntaxException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		final HttpClient httpclient = new DefaultHttpClient();
		HttpPost httppost = new HttpPost(url);
		if (headers != null)
			for (Map.Entry<String, String> en : headers.entrySet())
				httppost.addHeader(en.getKey(), en.getValue());
		StringEntity se = new StringEntity(stringBody);
		httppost.setEntity(se);
		return post(httpclient, httppost, url, connTimeOut, dataTranTimeOut);

	}

	public static QueryCombination executePut(Map<String, String> headers,
			String url, String stringBody) throws ClientProtocolException,
			IOException, URISyntaxException {
		return executePut(headers, url, stringBody, CONNECTION_TIMEOUT,
				DATA_TRANSMISSION_TIMEOUT);

	}

	public static QueryCombination executePut(Map<String, String> headers,
			String url, String stringBody, int connTimeOut, int dataTranTimeOut)
			throws ClientProtocolException, IOException, URISyntaxException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		final HttpClient httpclient = new DefaultHttpClient();
		HttpPut httpPut = new HttpPut(url);
		if (headers != null)
			for (Map.Entry<String, String> en : headers.entrySet())
				httpPut.addHeader(en.getKey(), en.getValue());

		StringEntity se = new StringEntity(stringBody);
		httpPut.setEntity(se);
		return put(httpclient, httpPut, url, connTimeOut, dataTranTimeOut);

	}

	public static QueryCombination executePost(String url,
			Map<String, ContentBody> body) throws ClientProtocolException,
			IOException, URISyntaxException {
		return executePost(url, body, CONNECTION_TIMEOUT,
				DATA_TRANSMISSION_TIMEOUT);
	}

	public static QueryCombination executePost(String url,
			Map<String, ContentBody> body, int connTimeOut, int dataTranTimeOut)
			throws ClientProtocolException, IOException, URISyntaxException {
		if (connTimeOut <= 0)
			connTimeOut = CONNECTION_TIMEOUT;
		if (dataTranTimeOut <= 0)
			dataTranTimeOut = DATA_TRANSMISSION_TIMEOUT;
		final HttpClient httpclient = new DefaultHttpClient();
		HttpPost httppost = new HttpPost(url);
		MultipartEntity reqEntity = new MultipartEntity();
		for (Map.Entry<String, ContentBody> e : body.entrySet())
			reqEntity.addPart(e.getKey(), e.getValue());
		httppost.setEntity(reqEntity);
		return post(httpclient, httppost, url, connTimeOut, dataTranTimeOut);

	}

	private static QueryCombination post(HttpClient httpclient,
			HttpPost httppost, String url, int connTimeOut, int dataTranTimeOut)
			throws ClientProtocolException, IOException {
		httpclient.getParams().setParameter(
				CoreConnectionPNames.CONNECTION_TIMEOUT, connTimeOut);
		httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
				dataTranTimeOut);
		HttpResponse response = httpclient.execute(httppost);
		QueryCombination q = combine(response);

		q.setUri(url);
		return q;
	}

	private static QueryCombination put(HttpClient httpclient, HttpPut httpPut,
			String url, int connTimeOut, int dataTranTimeOut)
			throws ClientProtocolException, IOException {

		httpclient.getParams().setParameter(
				CoreConnectionPNames.CONNECTION_TIMEOUT, connTimeOut);
		httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
				dataTranTimeOut);
		HttpResponse response = httpclient.execute(httpPut);
		QueryCombination q = combine(response);

		q.setUri(url);
		return q;
	}

	private static QueryCombination combine(HttpResponse response)
			throws ClientProtocolException, IOException {
		String responseString = EntityUtils.toString(response.getEntity());
		QueryCombination q = new QueryCombination();
		q.setStatus(response.getStatusLine().getStatusCode());

		q.setValueReturn(responseString);
		return q;
	}

	public void setPath(String path) {
		this.urlUtil.setPath( path);
	}

	public void setProtocol(String protocol) {
		this.urlUtil.setProtocol(protocol);
	}

	public void setPort(int port) {
		this.urlUtil.setPort( port);
	}

	public static URI assembleUri(String hostAndPath,
			Map<String, String> queryQarams) throws URISyntaxException {
		return assembleUri(null, null, -1, hostAndPath, queryQarams, null);

	}

	public static URI assembleUri(String host, String path,
			Map<String, String> queryQarams) throws URISyntaxException {
		return assembleUri(null, host, -1, path, queryQarams, null);

	}

	public static URI assembleUri(String scheme, String host, int port,
			String path, Map<String, String> queryQarams, String fragment)
			throws URISyntaxException {
		String protocol = "http";
		if (scheme != null)
			protocol = scheme;
		URI uri = URIUtils.createURI(protocol, host, port, path,
				generateQuery(queryQarams), fragment);
		return uri;
	}

	private static String generateQuery(Map<String, String> queryQarams) {
		if (queryQarams == null || queryQarams.size() == 0)
			return null;
		StringBuilder sb = new StringBuilder();
		for (Map.Entry<String, String> entry : queryQarams.entrySet()) {
			if (sb.length() > 0)
				sb.append("&");
			sb.append(entry.getKey()).append("=").append(entry.getValue());
		}
		if (sb.length() > 0)
			return sb.toString();
		return null;
	}

	public static class QueryCombination {
		private String uri;
		private String valueReturn;
		private Object attachment;
		private int status;

		public QueryCombination() {
		}

		public QueryCombination(String value) {
			this.valueReturn = value;
		}

		public QueryCombination(String url, String value) {
			this.uri = url;
			this.valueReturn = value;

		}

		public QueryCombination(URI uri, String value) {
			if (uri == null)
				throw new NullPointerException();
			this.uri = uri.toString();
			this.valueReturn = value;
		}

		public int getStatus() {
			return status;
		}

		public void setStatus(int status) {
			this.status = status;
		}

		public String getValueReturn() {
			return valueReturn;
		}

		public void setValueReturn(String valueReturn) {
			this.valueReturn = valueReturn;
		}

		public Object getAttachment() {
			return attachment;
		}

		public void setAttachment(Object attachment) {
			this.attachment = attachment;
		}

		public String getUri() {
			return uri;
		}

		public void setUri(String uri) {
			this.uri = uri;
		}

	}

	public static String traditionalMethod(String u) throws IOException {

		URL url = null;
		HttpURLConnection httpCon = null;
		url = new URL(u);
		httpCon = (HttpURLConnection) url.openConnection();
		httpCon.setUseCaches(false);
		httpCon.setDoInput(true);
		httpCon.setDoOutput(true);
		httpCon.setInstanceFollowRedirects(false);
		httpCon.setRequestMethod("GET");
		return IOUtils.toString(httpCon.getInputStream());
	}

}
